# vim:ts=4:sw=4:noet:
"""
Core compiler 
"""

import semantics
import parse
import codegen
import optimise
import logging

class Compiler:
	""" Core Compiler

		The main compiler class, of which only one should exist since it slowly
		accrues a Method IR cache used in optimisation.
	"""

	def __init__(self, mem, mcheck=True, allClasses=[], useMethCache = {}):
		self.mem = mem
		self.mcheck = mcheck
		self.allClasses = allClasses
		self.parser = parse.Parser(None, mem.status)
		self.checker = semantics.Checker(self.mem, mem.status, mcheck)
		self.checker.populateFromMem()
		self.cg = codegen.CodeGen(self.mem)
		self.irmethCache = useMethCache
		self.__precacheIRMethods()

	def __precacheIRMethods(self):
		""" cache IR Methods if we're passed full IR Classes (eg, imagegen.py) """
		for c in self.allClasses:
			mc = {}
			for m in c.meths:
				mc[m.name] = m
			self.irmethCache[c.name] = mc

	def checkMethodSig(self, sig):
		""" Check a method signature and return the method name """
		irmeth = self.parser.parseMethodFromSource("%s []" % sig)
		return irmeth.name

	def optimiseAndGenerateMethod(self, spycls, irmeth, updateDependants=True):
		""" Optimise and generate bytecodes for an IR Method """
		self.cg.setup()
		opt = optimise.Optimiser(self, self.mem, self.allClasses)
		opt.doIROpts(spycls.name, irmeth)
		irmeth.cfg()
		opt.doBBOpts(spycls.name, irmeth)
		irmeth.cg(self.mem, self.cg)
		self.cg.fixLabels()
		# If there's a super send in this method, append the methods class to the
		# literal array. The VM uses this to look up the superclass
		if self.cg.seenSuper:
			self.cg.literals.append(spycls)

		irmeth.spy.bytecodes = self.cg.bcs
		irmeth.spy.bclines = self.cg.bclines
		irmeth.spy.literals = self.cg.literals
		irmeth.spy.maxStack = self.cg.maxStack
		irmeth.spy.maxTemps = irmeth.maxTemps

		if updateDependants:
			#print "compiled %s, do depends:" % (irmeth.name)
			for depcls, depmeth in irmeth.spy.getDependants():
				c = Compiler(self.mem, True, [], useMethCache = self.irmethCache)
				source = "%s [ %s ]" % (depmeth.sig, depmeth.source)
				c.compileMethod(depcls, source, updateDependants=False)
				print "do dep for %s: %s>>%s" % (irmeth.name, depcls.name, depmeth.name)
		self.__cacheIRMethod(spycls.name, irmeth.name, irmeth)
		return irmeth.spy
		
	def compileMethod(self, spycls, source, updateDependants=True):
		""" Compile, optimise and generate bytecodes for method source 

		    If updateDependants is true, recompile all methods that depend on this
		    method, since they may have inlined the previous version of this method
		"""
		irmeth = self.parser.parseMethodFromSource(source)
		self.checker.checkSingleMethod(spycls, irmeth)
		oldDeps = irmeth.spy.revdependants
		irmeth.spy.clearRevDependants()
		ret = self.optimiseAndGenerateMethod(spycls, irmeth, updateDependants)
		for kd in oldDeps:
			depcls, depmeth = oldDeps[kd]
			if kd not in irmeth.spy.revdependants:
				print "removing old dependency:", kd
				depmeth.removeDependant(spycls, irmeth.spy)
		return ret

	def compileFile(self, sourcefile):
		""" Compile an entire Smalltalk sourcefile """
		classes = self.parser.parseFile(sourcefile)
		self.checker.check(classes)
		cname = ""
		for cls in classes:
			if cls.name[:4]!="Meta":
				cname = cls.name
			for meth in cls.meths:
				self.mem.addMessage(meth.name)
				self.optimiseAndGenerateMethod(cls.spy, meth, False)
		return cname

	def deleteCachedIRMethod(self, clsname, methname):
		if clsname not in self.irmethCache:
			return
		if methname not in self.irmethCache[clsname]:
			return
		del self.irmethCache[clsname][methname]

	def __cacheIRMethod(self, clsname, methname, irmeth):
		""" Add an IR Method to the cache """
		if clsname not in self.irmethCache:
			self.irmethCache[clsname] = {}
		self.irmethCache[clsname][methname] = irmeth

	def getIRForMethod(self, clsname, methname):
		""" Get IR Method from cache, or compile and cache it """
		if clsname in self.irmethCache:
			mc = self.irmethCache[clsname]
			if methname in mc:
				return mc[methname]

		spycls = self.mem.getGlobal(clsname)
		if spycls:
			spymeth = spycls.getMethod(methname)
			if spymeth:
				self.__cacheIRMethod(clsname, methname, None)
				source = "%s [ %s ]" % (spymeth.sig,spymeth.source)
				c = Compiler(self.mem, self.mcheck, [], useMethCache = self.irmethCache)
				c.compileMethod(spycls, source, updateDependants=False)
				return self.irmethCache[clsname][methname]
		logging.warn("getIRForMethod: no idea what to do")
		return None
